忍者刘米米

8. Closures

Closure

Closures can capture and store references to any constants and variables from the context in which they are defined. This is known as closing over those constants and variables.

Closure Expressions

.Sort

  • For characters in strings, “greater than” means “appears later in the alphabet than”. This means that the letter “B” is “greater than” the letter “A”, and the string “Tom” is greater than the string “Tim”.
  • The sorted(by:) method accepts a closure that takes two arguments of the same type as the array’s contents, and returns a Bool value to say whether the first value should appear before or after the second value once the values are sorted.
  • The sorting closure needs to return true if the first value should appear before the second value, and false otherwise.
1
2
3
4
5
6
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Closure Expression Syntax

  • The parameters in closure expression syntax can be in-out parameters, but they can’t have a default value.
  • Variadic parameters can be used if you name the variadic parameter.
  • Tuples can also be used as parameter types and return types.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{ (parameters) -> return type in
statements
}

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})

// Inferring Type From Context
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

// Implicit Returns from Single-Expression Closures
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// Shorthand Argument Names
// $0 and $1 refer to the closure’s first and second String arguments.
reversedNames = names.sorted(by: { $0 > $1 } )

Operator Methods

Swift’s String type defines its string-specific implementation of the greater-than operator (>) as a method that has two parameters of type String, and returns a value of type Bool.

1
reversedNames = names.sorted(by: >)

Trailing Closures

  • function’s final argument
  • don’t write the argument label for the closure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}

// Here's how you call this function without using a trailing closure:

someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})

// Here's how you call this function with a trailing closure instead:

someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}

reversedNames = names.sorted() { $0 > $1 }

If a closure expression is provided as the function or method’s only argument and you provide that expression as a trailing closure, you do not need to write a pair of parentheses ()

1
reversedNames = names.sorted { $0 > $1 }

.map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

Capturing Values

A closure can capture constants and variables from the surrounding context in which it is defined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

incrementByTen()
// returns a value of 40

Closures Are Reference Types

  • functions and closures are reference types.
  • Whenever you assign a function or a closure to a constant or a variable, you are actually setting that constant or variable to be a reference to the function or closure
  • This also means that if you assign a closure to two different constants or variables, both of those constants or variables will refer to the same closure:
1
2
3
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

Escaping Closures

  • A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.
  • One way that a closure can escape is by being stored in a variable that is defined outside the function
  • many functions that start an asynchronous operation take a closure argument as a completion handler. The function returns after it starts the operation, but the closure isn’t called until the operation is completed—the closure needs to escape, to be called later
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}

class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

Autoclosures

  • An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it.
  • It’s common to call functions that take autoclosures, but it’s not common to implement that kind of function.
  • An autoclosure lets you delay evaluation, because the code inside isn’t run until you call the closure.
  • Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.

Example 1

1
2
3
4
5
6
7
8
9
10
11
12
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

Example 2

1
2
3
4
5
6
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

@autoclosure

1
2
3
4
5
6
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

@autoclosure + @escaping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"